﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace Central.Linq {

	/// <summary>
	/// Определение фильтрации для запроса
	/// </summary>
	public class FilterManager {
		#region конструктор

		/// <summary>
		/// Создает экземпляр коллекции фильтров
		/// </summary>
		public FilterManager() {
			this.filterDefinitions = new Collection<FilterDefinition>();
			this.childFilterDefinitions = new Collection<FilterManager>();
			this.LogicalOperator = LogicalOperator.And;
		}

		/// <summary>
		/// Создает экземпляр коллекции фильтров c предустановленным LogicalOperator
		/// </summary>
		public FilterManager(LogicalOperator logicalOperator) {
			this.filterDefinitions = new Collection<FilterDefinition>();
			this.childFilterDefinitions = new Collection<FilterManager>();
			this.LogicalOperator = logicalOperator;
		}

		/// <summary>
		/// Создает экземпляр коллекции фильтров c предустановленным LogicalOperator и коллекцией фильтров текущего уровня
		/// </summary>
		public FilterManager(LogicalOperator logicalOperator, Collection<FilterDefinition> filterDefinitions) {
			this.filterDefinitions = filterDefinitions;
			this.childFilterDefinitions = new Collection<FilterManager>();
			this.LogicalOperator = logicalOperator;
		}

		/// <summary>
		/// Создает заполненный экземпляр коллекции фильтров
		/// </summary>
		public FilterManager(LogicalOperator logicalOperator, Collection<FilterDefinition> filterDefinitions, Collection<FilterManager> childFilterDefinitions) {
			this.filterDefinitions = filterDefinitions;
			this.childFilterDefinitions = childFilterDefinitions;
			this.LogicalOperator = logicalOperator;
		}

		#endregion конструктор

		#region [+] cвойства

		/// <summary>
		/// Логическая операция над коллекцией фильтров
		/// </summary>
		public LogicalOperator LogicalOperator { get; set; }

		private Collection<FilterDefinition> filterDefinitions;

		/// <summary>
		/// Коллекция фильтров
		/// </summary>
		public IEnumerable<FilterDefinition> FilterDefinitions { get { return filterDefinitions; } }

		private Collection<FilterManager> childFilterDefinitions;

		/// <summary>
		/// Список коллекций дочерних фильтров
		/// </summary>
		public IEnumerable<FilterManager> ChildFilterDefinitions { get { return childFilterDefinitions; } }

		private FilterPredicate predicateDefinition;

		/// <summary>
		/// Входной предикат фильтров
		/// </summary>
		public FilterPredicate PredicateDefinition {
			get {
				return this.predicateDefinition;
			}
			set {
				this.predicateDefinition = value;
			}
		}

		/// <summary>
		/// Выходной предикат фильтров
		/// </summary>
		public FilterPredicate Predicate {
			get {
				return this.GeneratePredicate();
			}
		}

		/// <summary>
		/// Возвращаемое количество фильтров
		/// </summary>
		public int Count {
			get {
				return
						(this.FilterDefinitions == null ? 0 : this.filterDefinitions.Count) +
						(this.ChildFilterDefinitions == null ? 0 : this.childFilterDefinitions.Count) +
						(this.PredicateDefinition != null && !string.IsNullOrEmpty(this.PredicateDefinition.Where) ? 1 : 0);
			}
		}

		#endregion [+] cвойства

		#region методы

		/// <summary>
		/// Сравнение объектов
		/// </summary>
		/// <param name="obj"></param>
		/// <returns></returns>
		public override bool Equals(object obj) {
			return Equals(obj as FilterManager);
		}

		/// <summary>
		/// Сравнение объектов FilterManager
		/// </summary>
		/// <param name="other"></param>
		/// <returns></returns>
		public bool Equals(FilterManager other) {
			if (other == null || other.LogicalOperator != this.LogicalOperator)
				return false;

			if ((other.FilterDefinitions == null) != (this.FilterDefinitions == null) ||
				(other.ChildFilterDefinitions == null) != (this.ChildFilterDefinitions == null) ||
				(other.PredicateDefinition == null) != (this.PredicateDefinition == null))
				return false;

			if (other.PredicateDefinition != null && this.PredicateDefinition != null) {
				if (!other.PredicateDefinition.Equals(this.PredicateDefinition))
					return false;
			}

			if (other.FilterDefinitions != null && this.FilterDefinitions != null) {
				if (!other.FilterDefinitions.OrderBy(o => o).SequenceEqual(this.FilterDefinitions.OrderBy(o => o)))
					return false;
			}

			if (other.ChildFilterDefinitions != null && this.ChildFilterDefinitions != null) {
				if (!other.ChildFilterDefinitions.OrderBy(o => o).SequenceEqual(this.ChildFilterDefinitions.OrderBy(o => o)))
					return false;
			}
			return true;
		}

		/// <summary>
		/// Получение Hash кода объекта FilterManager
		/// </summary>
		/// <returns></returns>
		public override int GetHashCode() {
			return this.LogicalOperator.GetHashCode() ^ this.PredicateDefinition.GetHashCode() ^ this.FilterDefinitions.GetHashCode() ^ this.ChildFilterDefinitions.GetHashCode();
		}

		/// <summary>
		/// Метод добавления FilterDefinition в коллекцию
		/// </summary>
		/// <param name="filterDefinition"></param>
		public void Add(FilterDefinition filterDefinition) {
			if (filterDefinition == null)
				return;

			this.filterDefinitions = this.filterDefinitions ?? new Collection<FilterDefinition>();

			if (!this.filterDefinitions.Contains(filterDefinition))
				this.filterDefinitions.Add(filterDefinition);
		}

		/// <summary>
		/// Метод удаления FilterDefinition из коллекции
		/// </summary>
		/// <param name="filterDefinition"></param>
		public void Remove(FilterDefinition filterDefinition) {
			if (filterDefinition == null)
				return;

			if (this.filterDefinitions != null)
				this.filterDefinitions.Remove(filterDefinition);
		}

		/// <summary>
		/// Метод очищения коллекции
		/// </summary>
		public void Clear() {
			if (this.filterDefinitions != null)
				this.filterDefinitions.Clear();
			if (this.childFilterDefinitions != null)
				this.childFilterDefinitions.Clear();
			this.PredicateDefinition = null;
		}

		/// <summary>
		/// Метод добавления ChildFilterDefinitions в коллекцию
		/// </summary>
		/// <param name="filters"></param>
		public void AddChild(FilterManager filters) {
			if (filters == null)
				return;

			this.childFilterDefinitions = this.childFilterDefinitions ?? new Collection<FilterManager>();

			if (!this.childFilterDefinitions.Contains(filters))
				this.childFilterDefinitions.Add(filters);
		}

		/// <summary>
		/// Метод удаления ChildFilterDefinitions из коллекции
		/// </summary>
		/// <param name="filters"></param>
		public void RemoveChild(FilterManager filters) {
			if (filters == null)
				return;

			if (this.childFilterDefinitions != null)
				this.childFilterDefinitions.Remove(filters);
		}

		#endregion методы

		#region скрытые методы

		/// <summary>
		/// Получение предиката фильтра
		/// </summary>
		/// <param name="indexPar">начальный индекс значения в коллекции</param>
		/// <returns>предикат фильтра</returns>
		private FilterPredicate GeneratePredicate() {
			string wherePredicate = "";
			StringBuilder where = new StringBuilder();

			#region получение предиката по PredicateDefinition

			FilterPredicate predicateResult;
			if (this.PredicateDefinition != null && !string.IsNullOrEmpty(this.PredicateDefinition.Where)) {
				lock (this.PredicateDefinition) {
					predicateResult = new FilterPredicate(this.PredicateDefinition.Where, this.PredicateDefinition.Values);
				}
			}
			else
				predicateResult = new FilterPredicate();

			#endregion получение предиката по PredicateDefinition

			#region получение предиката по FilterDefinitions

			if (this.FilterDefinitions != null) {
				lock (this.FilterDefinitions) {
					foreach (FilterDefinition item in this.FilterDefinitions) {
						// если строка фильтра пустая
						if (where.Length == 0) {
							wherePredicate = item.Predicate;
						}
						else {
							wherePredicate = String.Concat(" ", this.LogicalOperator, " ", item.Predicate);
						}

						// замена в строке фильтра индексов значений в соответствие с коллекцией values
						wherePredicate = Regex.Replace(wherePredicate, @"\x5BValue\x5D", delegate(Match match) {
							// индекс значения
							int indexValue = predicateResult.Add(item.Value);

							// Если значение найдено
							if (indexValue >= 0)
								return string.Concat("@", (indexValue).ToString(CultureInfo.CurrentCulture));
							else
								return match.Value;
						});

						where.Append(wherePredicate);
					}
				}
			}

			#endregion получение предиката по FilterDefinitions

			#region получение предиката по ChildFilterDefinitions

			if (this.ChildFilterDefinitions != null) {
				lock (this.ChildFilterDefinitions) {
					foreach (FilterManager item in this.ChildFilterDefinitions) {
						if (item.Count > 0) {
							FilterPredicate childPredicate = item.GeneratePredicate();
							if (childPredicate != null && !string.IsNullOrEmpty(childPredicate.Where) && childPredicate.Values != null)
								// замена в строке фильтра индексов значений в соответствие с коллекцией values
								childPredicate.Where = Regex.Replace(childPredicate.Where, @"@\d+", delegate(Match match) {
									// индекс значения
									int indexValueResult;

									if (int.TryParse(match.Value.Substring(1), out indexValueResult)) {
										if (childPredicate.Values.Count > indexValueResult) {
											object value = childPredicate.Values[indexValueResult];
											if (value != null) {
												indexValueResult = predicateResult.Add(value);
											}
										}
										return string.Concat("@", (indexValueResult).ToString(CultureInfo.CurrentCulture));
									}
									else
										return match.Value;
								});

							if (where.Length == 0) {
								wherePredicate = string.Concat("(", childPredicate.Where, ")");
							}
							else {
								wherePredicate = string.Concat(" ", this.LogicalOperator, " (", childPredicate.Where, ")");
							}

							where.Append(wherePredicate);
						}
					}
				}
			}

			#endregion получение предиката по ChildFilterDefinitions

			#region получение результирующего предиката фильтра

			// Если исходный фильтр пустой
			if (string.IsNullOrEmpty(predicateResult.Where)) {
				predicateResult.Where = where.ToString();
			}
			else {
				// Добавляем к получившемуся фильтру исходный по условию And
				if (where.Length > 0) {
					predicateResult.Where = String.Concat(predicateResult.Where, " ", LogicalOperator.And, " (", where.ToString(), ")");
				}
			}

			#endregion получение результирующего предиката фильтра

			return predicateResult;
		}

		#endregion скрытые методы
	}
}